local modeLocal = 0
local modeRemote = 1
local mode = 0
local yInitial = 79
local y = yInitial
local drillDepth = 0
local bedrockLevel = 5
local xOffset = 1
local yOffset = 3
local zOffset = 1
local frameWidth = 17
local frameDepth = 17
local frameHeight = 10
local wireForward = colors.red
local wireBack = colors.orange
local wireLeft = colors.yellow
local wireRight = colors.lime
local wireUp = colors.green
local wireDown = colors.cyan
local wireExtend = colors.lightBlue
local wireRetract = colors.blue
local wireDeploy = colors.purple
local wireBreak = colours.magenta
local wireMine = colors.pink
local wireFurnace = colors.brown
local moveTicks = 10
local moveDelay = 10
local mineTicks = 4
local cableSide = "top"
local modemSide
local windowWidth
local windowHeight
local textInput
local textInputLength = 0
local verbose = true
local running = false
local consoleText = { }
local modemConnected = "Modem connected successfully"
local modemMissing = "Modem missing, please attach modem"
local author = "Kevin Scroggins"
local nickname = "nitro glycerine"
local email = "nitro404@gmail.com
local website = "http://www.nitro404.com"

-- TODO add move arguments

function getInput(side)
  return redstone.getInput(side)
end

function setOutput(side, state)
  redstone.setOutput(side, state)
end

function sendPulse(side, ticks)
  local time
  if ticks < 1 then
    return
  else
    time = ticks * 0.05
  end
  
  redstone.setOutput(side, true)
  os.sleep(time)
  redstone.setOutput(side, false)
end

function sendPulse(side)
  sendPulse(side, 1)
end

function getBundledInput(side, wires)
  return colors.test(redstone.getBundledInput(side), wires)
end

function setBundledOutput(side, wires, value)
  if value == true then
    redstone.setBundledOutput(side, bit.bor(redstone.getBundledInput(side), wires))
  else
--    redstone.setBundledOutput(side, bit.xor(lastState, wires)) -- wrong (TODO) (not twice)
  end
end

function clearBundledOutput(side)
  redstone.setBundledOutput(side, 0)
end

function sendBundledPulse(side, wires)
  sendBundledPulse(side, wires, 1)
end

function sendBundledPulse(side, wires, ticks)
  local time
  if ticks < 0 then
    return
  else
    time = ticks * 0.05
  end
  
  local lastState = redstone.getBundledInput(side)
  
  redstone.setBundledOutput(side, bit.bor(lastState, wires))
  os.sleep(time)
  redstone.setBundledOutput(side, lastState)
end

function moveForward(amount)
  if amount == nil or amount == 0 then
    return
  elseif amount < 0 then
    moveBack(0 - amount)
  end
  
  local count = 0
  
  while true do
    if count == amount then
	  break
	end
    
    sendBundledPulse(cableSide, wireForward, moveTicks)
    os.sleep(moveTicks * 0.05)
    sendBundledPulse(cableSide, wireForward, moveTicks)
    os.sleep(moveTicks * 0.05)
	
    count = count + 1
  end
end

function moveBack(amount)
  if amount == nil or amount == 0 then
    return
  elseif amount < 0 then
    moveForward(0 - amount)
  end
  
  local count = 0
  
  while true do
    if count == amount then
	  break
	end
    
    sendBundledPulse(cableSide, wireBack, moveTicks)
    os.sleep(moveTicks * 0.05)
    sendBundledPulse(cableSide, wireBack, moveTicks)
    os.sleep(moveTicks * 0.05)
	
    count = count + 1
  end
end

function moveLeft(amount)
  if amount == nil or amount == 0 then
    return
  elseif amount < 0 then
    moveRight(0 - amount)
  end
  
  local count = 0
  
  while true do
    if count == amount then
	  break
	end
    
    sendBundledPulse(cableSide, wireLeft, moveTicks)
    os.sleep(moveTicks * 0.05)
    sendBundledPulse(cableSide, wireLeft, moveTicks)
    os.sleep(moveTicks * 0.05)
	
    count = count + 1
  end
end

function moveRight(amount)
  if amount == nil or amount == 0 then
    return
  elseif amount < 0 then
    moveLeft(0 - amount)
  end
  
  local count = 0
  
  while true do
    if count == amount then
	  break
	end
    
    sendBundledPulse(cableSide, wireRight, moveTicks)
    os.sleep(moveTicks * 0.05)
    sendBundledPulse(cableSide, wireRight, moveTicks)
    os.sleep(moveTicks * 0.05)
	
    count = count + 1
  end
end

function moveUp(amount, updateInitial)
  if amount == nil or amount == 0 then
    return
  elseif amount < 0 then
    moveDown(0 - amount, updateInitial)
  end
  
  local count = 0
  
  while true do
    if count == amount then
	  break
	end
    
    sendBundledPulse(cableSide, wireUp, moveTicks)
    os.sleep(moveTicks * 0.05)
    sendBundledPulse(cableSide, wireUp, moveTicks)
    os.sleep(moveTicks * 0.05)
  
    if updateInitial == true then
      yInitial = yInitial + 1
      y = yInitial
    end
	
    count = count + 1
  end
end

function moveDown(amount, updateInitial)
  if amount == nil or amount == 0 then
    return
  elseif amount < 0 then
    moveUp(0 - amount, updateInitial)
  end
  
  local count = 0
  
  while true do
    if count == amount then
	  break
	end
    
    sendBundledPulse(cableSide, wireDown, moveTicks)
    os.sleep(moveTicks * 0.05)
    sendBundledPulse(cableSide, wireDown, moveTicks)
    os.sleep(moveTicks * 0.05)
  
    if updateInitial == true then
      yInitial = yInitial - 1
	
	-- TODO check for < bedrockLevel w/ offset
	
	  y = yInitial
    end
	
    count = count + 1
  end
end

function extend(amount)
  if amount == nil or amount == 0 then
    return
  elseif amount < 0 then
    retract(0 - amount)
  end
  
  local count = 0
  
  while true do
    if count == amount then
	  break
	end
    
    sendBundledPulse(cableSide, wireExtend, moveTicks)
    os.sleep(moveTicks * 0.05)
	
	sendBundledPulse(cableSide, wireDeploy, moveTicks)
    os.sleep(moveTicks * 0.05)
    
	drillDepth = drillDepth + 1
	
	-- TODO check for < bedrockLevel w/ offset (ship bounding box and drill offset)
	
    count = count + 1
  end
end

function retract(amount)
  if amount == nil or amount == 0 then
    return
  elseif amount < 0 then
    extend(0 - amount)
  end
  
  local count = 0
  
  while true do
    if count == amount then
	  break
	end
	
	sendBundledPulse(cableSide, wireBreak, moveTicks)
    os.sleep(moveTicks * 0.05)
    
    sendBundledPulse(cableSide, wireRetract, moveTicks)
    os.sleep(moveTicks * 0.05)
    
	drillDepth = drillDepth - 1
	
	-- TODO check for == 0 w/ offset (ship bounding box and drill offset)
	
    count = count + 1
  end
end

function mine()
  sendBundledPulse(cableSide, wireMine, mineTicks)
  os.sleep(mineTicks * 0.05)
end

--[[
function mineToBedrock()
  while true do
    if y < bedrockLevel or y - yOffset <= bedrockLevel then
      break
    end
	
	moveDown(1, false)
	mine()
	
	y = y - 1
  end
end

function returnToSurface()
  while true do
-- TODO: check bounding box & max height
    if yInitial - yOffset <= bedrockLevel or y >= yInitial then
	  break
	end
	
	moveUp(1, false)
	
	y = y + 1
  end
end

function mineQuarry()
  if verbose == true then
    addText("Mining to bedrock")
  end
  
  mine()
  
  mineToBedrock()
  
  if verbose == true then
    addText("Mining completed, returning to surface")
  end
  
  os.sleep(moveTicks * 0.05)
  
  returnToSurface()
  
  if verbose == true then
    addText("Returned to surface")
  end
end
--]]

function sendMessage(s)
  if s == nil then
    return
  end
  
  rednet.broadcast(s)
end

function receiveMessage()
  local senderID, message, distance
  
  while running == true do
    handleMessage(rednet.receive())
  end
end

function handleMessage(senderID, message, distance)
  local cmd
  
  if mode == modeRemote then
    if senderID == nil or message == nil or senderID == os.computerID() then
      return
    end
  end
  
  cmd = parseCommand(message)
  
  if cmd == nil or cmd[0] == nil or cmd[0] == "" then
    return
  elseif cmd[0]:lower() == "frame" then
    if #cmd == 0 or cmd[1] == nil or cmd[1] == "" or cmd[1] == "?" then
      if mode == modeRemote then
	    sendMessage("frame operational")
	  end
	
    elseif cmd[1]:lower() == "forward" then
      local amount = 1
	  if #cmd == 2 or cmd[2] ~= nil and cmd[2] ~= "" then
	    amount = tonumber(cmd[2])
	  end
      if verbose == true then
        addText("Frame moving forward " .. amount .. " block(s)")
      end
      moveForward(amount)
	  
    elseif cmd[1]:lower() == "back" then
      local amount = 1
	  if #cmd == 2 or cmd[2] ~= nil and cmd[2] ~= "" then
	    amount = tonumber(cmd[2])
	  end
      if verbose == true then
        addText("Frame moving back " .. amount .. " block(s)")
      end
      moveBack(amount)
	  
    elseif cmd[1]:lower() == "left" then
      local amount = 1
	  if #cmd == 2 or cmd[2] ~= nil and cmd[2] ~= "" then
	    amount = tonumber(cmd[2])
	  end
      if verbose == true then
        addText("Frame moving left " .. amount .. " block(s)")
      end
      moveLeft(amount)
	  
    elseif cmd[1]:lower() == "right" then
      local amount = 1
	  if #cmd == 2 or cmd[2] ~= nil and cmd[2] ~= "" then
	    amount = tonumber(cmd[2])
	  end
      if verbose == true then
        addText("Frame moving right " .. amount .. " block(s)")
      end
      moveRight(amount)
	
    elseif cmd[1]:lower() == "up" then
	  local amount = 1
	  if #cmd == 2 or cmd[2] ~= nil and cmd[2] ~= "" then
	    amount = tonumber(cmd[2])
	  end
      if verbose == true then
        addText("Frame moving up " .. amount .. " block(s)")
      end
      moveUp(amount, true)
	  
    elseif cmd[1]:lower() == "down" then
	  local amount = 1
	  if #cmd == 2 or cmd[2] ~= nil and cmd[2] ~= "" then
	    amount = tonumber(cmd[2])
	  end
      if verbose == true then
        addText("Frame moving down " .. amount .. " block(s)")
      end
      moveDown(amount, true)
	
	elseif cmd[1]:lower() == "extend" then
	  local amount = 1
	  if #cmd == 2 or cmd[2] ~= nil and cmd[2] ~= "" then
	    amount = tonumber(cmd[2])
	  end
      if verbose == true then
        addText("Extending drill down " .. amount .. " block(s)")
      end
      extend(amount)
	
	elseif cmd[1]:lower() == "retract" then
	  local amount = 1
	  if #cmd == 2 or cmd[2] ~= nil and cmd[2] ~= "" then
	    amount = tonumber(cmd[2])
	  end
      if verbose == true then
        addText("Retracting drill up " .. amount .. " block(s)")
      end
      retract(amount)
    	
	elseif cmd[1]:lower() == "mine" then
      if verbose == true then
        addText("Frame mining quarry")
      end
      mine()
	
--[[
    elseif cmd[1]:lower() == "mine" then
      if verbose == true then
        addText("Frame mining quarry")
      end
      mineQuarry()
-]]
	
    elseif cmd[1]:lower() == "operational" then
	  if mode == modeRemote then
        addText("Frame is operational")
	  end
	  
    else
      addText("Illegal usage of frame command: " .. cmd[1])
    end
  else
    addText("Received unknown command: \"" .. cmd[0] .. "\" from " .. senderID)
  end
end

function handleTextInput()
  while running == true do
    textInput = read()
    
    if verbose == true then
      addText(textInput)
    end
    
    if textInput:lower() == "quit" or textInput:lower() == "exit" or textInput:lower() == "stop" then
      stop()
    else
	  if mode == modeLocal then
        handleMessage(os.getComputerID(), textInput, 0)
	  elseif mode == modeRemote then
	    sendMessage(textInput)
	  end
    end
    
    textInputLength = 0
    
    draw()
  end
end

function keyListener()
  local t, k
  
  while running == true do
    t, k = os.pullEvent("key")
    if k == 28 then -- enter
      textInputLength = 0
    elseif k == 14 then -- backspace
      if textInputLength ~= 0 then
        textInputLength = textInputLength - 1
      end
    else
      textInputLength = textInputLength + 1
    end
  end
end

function parseCommand(s)
  local cmd = {}
  
  if s == nil then
    return
  end
  
  local i = 0
  for v in string.gmatch(s, "[^ \t]+") do
    cmd[i] = v
    i = i + 1
  end
  
  return cmd
end

function findPeripheral(p)
  for _, v in ipairs(rs.getSides()) do
    if peripheral.isPresent(v) and peripheral.getType(v) == p then
      return v
    end
  end
end

function addText(s)
  table.insert(consoleText, s)
  
  if #consoleText == windowHeight then
    table.remove(consoleText, 1)
  end
  
  draw()
end

function draw()
  term.clear()
  
  for i = 1, #consoleText, 1 do
    term.setCursorPos(1, i)
    term.write(consoleText[i])
  end
  
  term.setCursorPos(1, windowHeight)
  term.write(">")
  term.setCursorPos(textInputLength + 3, windowHeight)
end

function initModem()
  modemSide = findPeripheral("modem")
  if modemSide == nil then
    return false
  end
  
  rednet.open(modemSide)
  
  return rednet.isOpen(modemSide)
end

function init()
  windowWidth, windowHeight = term.getSize()
  
  if initModem() == true then
    if verbose == true then
      addText(modemConnected)
    end
  else
    if verbose == true then
      addText(modemMissing)
    end
    
    return false
  end
  
  clearBundledOutput(cableSide)
  
  running = true
    
  return true
end

function stop()
  running = false
  
  rednet.close(modemSide)
  os.reboot()
end

function frameController()
  term.clear()
  term.setCursorPos(1, 1)
  
  if init() ~= true then
    print("Initialization failed")
    return
  end
  
  addText("Frame Controller Initialized")
  
  if mode == modeLocal then
    parallel.waitForAll(draw, handleTextInput, keyListener)
  elseif mode == modeRemote then
    parallel.waitForAll(receiveMessage, draw, handleTextInput, keyListener)
  end
end

frameController()
